using System;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;

namespace Reflective
{
    /// <summary>
    /// This class provides several static methods for creating <see cref="TypeBuilder"/> objects.
    /// </summary>
    public static class TypeBuilderFactory
    {
        /// <summary>
        /// Creates a dynamic assembly and a new type in that assembly,
        /// for a specific interface, ready to be populated with
        /// implementations of the interface methods.
        /// </summary>
        /// <param name="interfaceType">
        /// The interface that the type will implement.
        /// </param>
        /// <param name="baseType">
        /// The <see cref="Type"/> of the class to descend from.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created type.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="interfaceType"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="baseType"/> is <c>null</c>.</para>
        /// </exception>
        public static TypeBuilder CreateDynamicClassForInterface(Type interfaceType, Type baseType)
        {
            if (interfaceType == null)
                throw new ArgumentNullException("interfaceType");
            if (baseType == null)
                throw new ArgumentNullException("baseType");

            var type = CreateDynamicClass(interfaceType.ToStringEx(), baseType);
            type.AddInterfaceImplementation(interfaceType);
            return type;
        }

        /// <summary>
        /// Creates a dynamic assembly and a new type in that assembly,
        /// for a specific interface, ready to be populated with
        /// implementations of the interface methods.
        /// </summary>
        /// <param name="interfaceType">
        /// The interface that the type will implement.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created type.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="interfaceType"/> is <c>null</c>.</para>
        /// </exception>
        public static TypeBuilder CreateDynamicClassForInterface(Type interfaceType)
        {
            if (interfaceType == null)
                throw new ArgumentNullException("interfaceType");

            var type = CreateDynamicClass(interfaceType.ToStringEx() + "Impl");
            type.AddInterfaceImplementation(interfaceType);
            return type;
        }

        /// <summary>
        /// Creates a new dynamic assembly and class and returns the
        /// <see cref="TypeBuilder"/> that can be used to add code to the
        /// new class.
        /// </summary>
        /// <param name="typeName">
        /// The name of the class to create, a full name with namespace
        /// and generic modifiers, if required.
        /// </param>
        /// <param name="baseType">
        /// The <see cref="Type"/> of the class to descend from.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created class.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="typeName"/> is <c>null</c> or empty.</para>
        /// <para>- or -</para>
        /// <para><paramref name="baseType"/> is <c>null</c>.</para>
        /// </exception>
        public static TypeBuilder CreateDynamicClass(string typeName, Type baseType)
        {
            if (StringEx.IsNullOrWhiteSpace(typeName))
                throw new ArgumentNullException("typeName");

            return InternalCreateDynamicType(typeName, baseType, true);
        }

        /// <summary>
        /// Creates a new dynamic assembly and class and returns the
        /// <see cref="TypeBuilder"/> that can be used to add code to the
        /// new class.
        /// </summary>
        /// <param name="typeName">
        /// The name of the class to create, a full name with namespace
        /// and generic modifiers, if required.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created class.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="typeName"/> is <c>null</c> or empty.</para>
        /// </exception>
        public static TypeBuilder CreateDynamicClass(string typeName)
        {
            return InternalCreateDynamicType(typeName, null, true);
        }

        /// <summary>
        /// Creates a new dynamic assembly and interface and returns the
        /// <see cref="TypeBuilder"/> that can be used to add code to the
        /// new class.
        /// </summary>
        /// <param name="typeName">
        /// The name of the interface to create, a full name with namespace
        /// and generic modifiers, if required.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created interface.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="typeName"/> is <c>null</c> or empty.</para>
        /// </exception>
        public static TypeBuilder CreateDynamicInterface(string typeName)
        {
            return InternalCreateDynamicType(typeName, null, false);
        }

        /// <summary>
        /// Creates a new dynamic assembly and type and returns the
        /// <see cref="TypeBuilder"/> that can be used to add code to the
        /// new type.
        /// </summary>
        /// <param name="typeName">
        /// The name of the type to create, a full name with namespace
        /// and generic modifiers, if required.
        /// </param>
        /// <param name="baseType">
        /// The <see cref="Type"/> of the class to descend from.
        /// </param>
        /// <param name="typeAsClass">
        /// If <c>true</c>, then define the type as a class; otherwise, as an
        /// interface.
        /// </param>
        /// <returns>
        /// The <see cref="TypeBuilder"/> for the created type.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="typeName"/> is <c>null</c> or empty.</para>
        /// </exception>
        private static TypeBuilder InternalCreateDynamicType(string typeName, Type baseType, bool typeAsClass)
        {
            if (StringEx.IsNullOrWhiteSpace(typeName))
                throw new ArgumentNullException("typeName");

            string assemblyNameTemp = typeName;
            assemblyNameTemp = assemblyNameTemp.Replace("<", "_").Replace(",", "_").Replace(">", "_");
            var assemblyName = new AssemblyName(String.Format(CultureInfo.InvariantCulture, "tmp_{0}", assemblyNameTemp));
            string moduleName = String.Format(CultureInfo.InvariantCulture, "{0}.dll", assemblyName.Name);

            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.RunAndSave);
            var module = assembly.DefineDynamicModule(moduleName, false);
            typeName = typeName.Replace("<", "_").Replace(",", "_").Replace(">", "_");
            TypeAttributes typeAttributes;
            if (typeAsClass)
                typeAttributes = TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public;
            else
                typeAttributes = TypeAttributes.Interface | TypeAttributes.Abstract | TypeAttributes.Public;

            if (baseType == null)
                return module.DefineType(typeName, typeAttributes);
            
            return module.DefineType(typeName, typeAttributes, baseType);
        }
    }
}
